import { Contact } from '../../services/contact';
import { SendBirdDevice } from './sendbird-device';
import {
  ContactState,
  DirectionType,
  ContactErrorType,
  ContactEventType,
  MessageType
} from '../../common/commlib.enums';
import {
  IMessageParams,
  IMessage,
  ISender
} from '../../common/commlib.interfaces';
import {
  FileMessage,
  GroupChannel,
  SendBirdInstance,
  SendBirdError,
  User,
  PreviousMessageListQuery,
  AdminMessage,
  UserMessage
} from 'sendbird';
import { Util } from '../../common/util';

export class SendBirdContact extends Contact {
  private sendbirdInstance: SendBirdInstance = null;
  private prevMessagesQuery: PreviousMessageListQuery;

  // Also record aws device directly to call aws specified API
  constructor(
    private direction: DirectionType,
    rawContact: GroupChannel,
    sendbirddevice: SendBirdDevice,
    initializeState?: ContactState
  ) {
    super(rawContact.url, sendbirddevice, rawContact);
    this.sendbirdInstance = sendbirddevice.getSendBirdInstance();
    this.state = initializeState || ContactState.UNKNOWN;

    this.initContact();
    this.subscribeEvents();
  }

  public accept(): any {
    this.rawContact.join((channel: GroupChannel, error: SendBirdError) => {
      if (error) {
        this.onError$.next({
          type: ContactErrorType.JOIN_CONTACT_FAILED,
          message: `Join contact ${channel.name} failed.`,
          payload: error
        });
      } else {
        this.onNewEvent$.next({
          eventType: ContactEventType.CONNECTED,
          payload: this
        });
      }
    });
    return this;
  }

  public getDirection() {
    return this.direction;
  }

  public decline(): any {
    return this;
  }

  public mute(): any {
    return this;
  }

  public unMute(): any {
    return this;
  }

  public hold(): any {
    return this;
  }

  public unHold(): any {
    return this;
  }

  public hangup(): any {
    if (!this.sendbirdInstance || !this.rawContact) {
      this.onError$.next({
        type: ContactErrorType.CONNECT_FAILED,
        message: 'Please register SendBird device first.',
        payload: 'No sendbird connnection'
      });
      return;
    }
    this.rawContact.leave((_response: any, error: any) => {
      if (error) {
        this.onError$.next({
          type: ContactErrorType.HANGUP_FAILED,
          message: 'Hangup contact failed from sendbird',
          payload: error
        });
        return;
      }
      this.state = ContactState.ENDED;
      this.onNewEvent$.next({
        eventType: ContactEventType.DISCONNECTED,
        payload: this
      });
    });
    return this;
  }

  public sendMessage(msgParams: IMessageParams): any {
    if (!msgParams || (!msgParams.messageContent && !msgParams.data)) {
      this.onError$.next({
        type: ContactErrorType.INVALID_MSG,
        message: 'Please enter message or data you want to send.',
        payload: 'Invalid message'
      });
      return;
    }

    if (!this.sendbirdInstance || !this.rawContact) {
      this.onError$.next({
        type: ContactErrorType.CONNECT_FAILED,
        message: 'Please register SendBird device first.',
        payload: 'No sendbird connnection'
      });
    } else {
      this.rawContact.sendUserMessage(
        msgParams.messageContent,
        msgParams.data,
        msgParams.msgCustomType,
        (msg: any, error: SendBirdError) => {
          if (error) {
            this.onError$.next({
              type: ContactErrorType.SEND_MSG_FAILED,
              message: 'Send message failed.',
              payload: error
            });
          } else {
            this.rawContact.endTyping();
            this.onNewEvent$.next({
              eventType: ContactEventType.NEW_MESSAGE,
              payload: this.convertMsg(msg)
            });
          }
        }
      );
    }
  }

  public typing(isTyping: boolean): any {
    if (!this.sendbirdInstance || !this.rawContact) {
      return;
    }
    if (isTyping) {
      this.rawContact.startTyping();
    } else {
      this.rawContact.endTyping();
    }
  }

  public getPreviousMessages(parameters: any): any {
    if (!this.sendbirdInstance || !this.rawContact) {
      this.onError$.next({
        type: ContactErrorType.CONNECT_FAILED,
        message: 'Please register SendBird device first.',
        payload: 'No sendbird connnection'
      });
      return;
    }

    let messageLimit: number;
    if (this.prevMessagesQuery === null || parameters.forceNewQuery) {
      this.prevMessagesQuery = this.rawContact.createPreviousMessageListQuery();
      this.prevMessagesQuery.limit = parameters.limit || 20;
      this.prevMessagesQuery.reverse = parameters.reverse || false;
      messageLimit = parameters.limit || 20;
    } else {
      messageLimit = this.prevMessagesQuery.limit;
    }

    this.prevMessagesQuery.load(
      (
        messages: Array<AdminMessage | UserMessage | FileMessage>,
        error: SendBirdError
      ) => {
        if (error) {
          this.onError$.next({
            type: ContactErrorType.LOAD_MSGS_FAILED,
            message: 'Retrieve messages failed',
            payload: error
          });
          return;
        }

        if (messages.length === messageLimit) {
          this.onNewEvent$.next({
            eventType: ContactEventType.MESSAGES_MORE,
            payload: true
          });
        } else {
          this.onNewEvent$.next({
            eventType: ContactEventType.MESSAGES_MORE,
            payload: false
          });
        }

        this.rawContact.markAsRead();

        const prevMsgs = messages.map((msg: any) => {
          return this.convertMsg(msg);
        });
        this.onNewEvent$.next({
          eventType: ContactEventType.MESSAGES_LOADED,
          payload: prevMsgs
        });
      }
    );
  }

  private initContact(): void {
    if (!this.rawContact) {
      return;
    }

    this.queueName = this.rawContact.name || 'unknown';
    this.fromAddress = this.rawContact.inviter
      ? this.rawContact.inviter.userId || 'unknow'
      : 'unknown';
    this.createdAt = this.rawContact.createdAt || new Date().getTime();
    this.createdBy = this.fromAddress;
  }

  private subscribeEvents(): void {
    if (!this.rawContact || !this.device) {
      return;
    }

    const msgReceivedHandler = new this.sendbirdInstance.ChannelHandler();
    msgReceivedHandler.onMessageReceived = this.onMsgRecieved.bind(this);
    this.sendbirdInstance.addChannelHandler(
      'CS_COMMLIB_MESSAGE_RECEIVED_HANDLER_ID',
      msgReceivedHandler
    );

    const typingHandler = new this.sendbirdInstance.ChannelHandler();
    typingHandler.onTypingStatusUpdated = this.onTypingUpdated.bind(this);
    this.sendbirdInstance.addChannelHandler(
      'CS_COMMLIB_TYPING_INDICATOR_HANDLER_ID',
      typingHandler
    );

    const msgUpdatedHandler = new this.sendbirdInstance.ChannelHandler();
    msgUpdatedHandler.onMessageUpdated = this.onMsgUpdated.bind(this);
    this.sendbirdInstance.addChannelHandler(
      'CS_COMMLIB_MESSAGE_UPDATED_HANDLER_ID',
      msgUpdatedHandler
    );

    const userJoinedHandler = new this.sendbirdInstance.ChannelHandler();
    userJoinedHandler.onUserJoined = this.onUserJoined.bind(this);
    this.sendbirdInstance.addChannelHandler(
      'CS_COMMLIB_USER_JOINED_HANDLER_ID',
      userJoinedHandler
    );

    const userLeftHandler = new this.sendbirdInstance.ChannelHandler();
    userLeftHandler.onUserLeft = this.onUserLeft.bind(this);
    this.sendbirdInstance.addChannelHandler(
      'CS_COMMLIB_USER_LEFT_HANDLER_ID',
      userLeftHandler
    );

    const msgDeletedHandler = new this.sendbirdInstance.ChannelHandler();
    msgDeletedHandler.onMessageDeleted = this.onMessageDeleted.bind(this);
    this.sendbirdInstance.addChannelHandler(
      'CS_COMMLIB_MESSAGE_DELETED_HANDLER_ID',
      msgDeletedHandler
    );
  }

  private onMsgRecieved(channel: GroupChannel, msg: any): void {
    if (!this.rawContact || channel.url !== this.rawContact.url) {
      return;
    }
    const newMsg = this.convertMsg(msg);
    this.onNewEvent$.next({
      eventType: ContactEventType.NEW_MESSAGE,
      payload: newMsg
    });
  }

  private onTypingUpdated(channel: GroupChannel): void {
    if (channel.isTyping()) {
      this.onNewEvent$.next({
        eventType: ContactEventType.TYPING,
        payload: true
      });
    } else {
      this.onNewEvent$.next({
        eventType: ContactEventType.TYPING,
        payload: false
      });
    }
  }

  private onMsgUpdated(channel: GroupChannel, msg: any): void {
    if (!this.rawContact || channel.url !== this.rawContact.url) {
      return;
    }
    const newMsg = this.convertMsg(msg);
    this.onNewEvent$.next({
      eventType: ContactEventType.MESSAGE_MODIFIED,
      payload: newMsg
    });
  }

  private onMessageDeleted(channel: GroupChannel, messageId: string): void {
    if (!this.rawContact || channel.url !== this.rawContact.url) {
      return;
    }
    this.onNewEvent$.next({
      eventType: ContactEventType.MESSAGE_REMOVED,
      payload: messageId
    });
  }

  private onUserJoined(channel: GroupChannel, user: User): void {
    if (!this.rawContact || channel.url !== this.rawContact.url) {
      return;
    }
    const newUser: ISender = user;
    this.onNewEvent$.next({
      eventType: ContactEventType.USER_JOINED,
      payload: newUser
    });
  }

  private onUserLeft(channel: GroupChannel, user: User): void {
    if (!this.rawContact || channel.url !== this.rawContact.url) {
      return;
    }
    const newUser: ISender = user;
    this.onNewEvent$.next({
      eventType: ContactEventType.USER_LEFT,
      payload: newUser
    });
  }

  private convertMsg(message: any): IMessage {
    const newMessage: IMessage = {
      id: message.messageId,
      contactId: message.channelUrl,
      index: new Date().getTime(),
      content: message.message,
      createdAt: message.createdAt,
      updatedAt: message.updatedAt,
      format: message.messageType,
      type: MessageType.MESSAGE, // TODO map type from sendbird to FSM
      sender: message.sender.userId,
      additionalProperties: Util.convertToJsonObject(message.data)
    };
    if (message.isFileMessage && message.isFileMessage()) {
      const fileMessage = message as FileMessage;
      newMessage.additionalProperties.thumbnail =
        fileMessage.thumbnails && fileMessage.thumbnails.length > 0
          ? fileMessage.thumbnails[0]
          : null;
    }

    return newMessage;
  }
}
